#include "sdk.h"

#include "find_replace.h"

#ifndef CB_PRECOMP
    #include <wx/dir.h>
    #include <wx/regex.h>

    #include "cbauibook.h"
    #include "cbeditor.h"
    #include "cbproject.h"
    #include "editormanager.h"
    #include "infowindow.h"
    #include "projectfile.h"
#endif

#include <wx/progdlg.h>

#include "annoyingdialog.h"
#include "cbstyledtextctrl.h"
#include "confirmreplacedlg.h"
#include "encodingdetector.h"
#include "findreplacedlg.h"
#include "searchresultslog.h"

struct cbFindReplaceData
{
    int start;
    int end;
    wxString findText;
    wxString replaceText;
    bool initialreplacing;
    bool findInFiles;
    bool delOldSearches;
    bool sortSearchResult;
    bool matchWord;
    bool startWord;
    bool startFile; //!< 待实施
    bool matchCase;
    bool regEx;
    bool directionDown;
    bool originEntireScope;
    int scope;
    wxString searchPath;
    wxString searchMask;
    int searchProject;
    int searchTarget;
    bool recursiveSearch;
    bool hiddenSearch;
    //!< only true when a new search has been started
    //!< 只有在启动新搜索时才为true
    bool NewSearch;
    //!< keep track of the start of a 'search' selection
    //!< 跟踪"搜索"选择的开始
    int SearchInSelectionStart;
    //!< keep track of the end of a 'search' selection
    //!< 跟踪"搜索"所选内容的结尾
    int SearchInSelectionEnd;
    bool autoWrapSearch;
    bool findUsesSelectedText;
    //!< for multi-line S&R.
    //!< 用于多线S&R
    bool multiLine;
    bool fixEOLs; //!< for multi-line S&R. Fixes EOLs in all the files searched.
    int eolMode; //!< for multi-line S&R

    void ConvertEOLs(int newmode);
    bool IsMultiLine();

    cbFindReplaceData()
    {
        eolMode = wxSCI_EOL_LF;
        fixEOLs = false;
    }
};

void cbFindReplaceData::ConvertEOLs(int newmode) {
    if (eolMode != newmode) {
        const wxChar *eol_lf = _T("\n");
        const wxChar *eol_crlf = _T("\r\n");
        const wxChar *eol_cr = _T("\r");

        const wxChar *eol_from = eol_lf;
        const wxChar *eol_to = eol_lf;
        switch (eolMode) {
            case wxSCI_EOL_CR:
                eol_from = eol_cr;
                break;
            case wxSCI_EOL_CRLF:
                eol_from = eol_crlf;
                break;
            default:;
        }
        switch (newmode) {
            case wxSCI_EOL_CR:
                eol_to = eol_cr;
                break;
            case wxSCI_EOL_CRLF:
                eol_to = eol_crlf;
                break;
            default:
                newmode = wxSCI_EOL_LF;
        }
        findText.Replace(eol_from, eol_to, true);
        replaceText.Replace(eol_from, eol_to, true);
        eolMode = newmode;
    }
}

bool cbFindReplaceData::IsMultiLine() {
    // For regex always assume multiline if the multiline checkbox is enabled because the user can enter "\n" to search for newlines
    // 对于正则表达式(regex),如果启用了多行复选框,则始终假定多行,因为用户可以输入"\n"来搜索换行
    if (regEx) { return multiLine; }
    // otherwise only treat the search as a multiline search only if there are newline characters in the search string
    // 否则,仅当搜索字符串中有换行符时,才将搜索视为多行搜索
    return ((findText.Find(_T("\n")) != wxNOT_FOUND) || (findText.Find(_T("\r")) != wxNOT_FOUND));
}

FindReplace::FindReplace() :
    m_LastFindReplaceData(nullptr)
{
}

FindReplace::~FindReplace()
{
    delete m_LastFindReplaceData;
}

void FindReplace::CreateSearchLog() {
    if (Manager::IsBatchBuild()) { return; }

    wxArrayInt widths;
    wxArrayString titles;
    titles.Add(wxT("文件"));
    titles.Add(wxT("行"));
    titles.Add(wxT("文本"));
    widths.Add(128);
    widths.Add(48);
    widths.Add(640);

    wxString prefix = ConfigManager::GetDataFolder() + _T("/images/16x16/");
    wxBitmap *bmp = new wxBitmap(cbLoadBitmap(prefix + _T("filefind.png"), wxBITMAP_TYPE_PNG));

    m_pSearchLog = new cbSearchResultsLog(titles, widths);
    CodeBlocksLogEvent evt(cbEVT_ADD_LOG_WINDOW, m_pSearchLog, wxT("搜索结果"), bmp);
    Manager::Get()->ProcessEvent(evt);
    Manager::Get()->SetSearchResultLogger(m_pSearchLog);
}

void FindReplace::LogSearch(const wxString& file, int line, const wxString& lineText) {
    wxArrayString values;
    wxString lineTextL;
    wxString lineStr;

    // line number -1 is used for empty string
    // 行号-1用于空字符串
    if (line != -1) { lineStr.Printf(_T("%d"), line); }
    else { lineStr.Printf(_T(" ")); }

    lineTextL = lineText;
    lineTextL.Replace(_T("\t"), _T(" "));
    lineTextL.Replace(_T("\r"), _T(" "));
    lineTextL.Replace(_T("\n"), _T(" "));
    lineTextL.Trim(false);
    lineTextL.Trim(true);
    if (lineTextL.Length() > 300) { lineTextL.Truncate(280) += _T("..."); }

    values.Add(file);
    values.Add(lineStr);
    values.Add(lineTextL);

    m_pSearchLog->Append(values, line == -1 ? Logger::caption : Logger::info);
}

int FindReplace::ShowFindDialog(bool replace, bool explicitly_find_in_files) {
    wxString phraseAtCursor;
    bool hasSelection = false;
    cbStyledTextCtrl *control = nullptr;

    EditorManager *editorMgr = Manager::Get()->GetEditorManager();

    cbEditor *ed = editorMgr->GetBuiltinEditor(editorMgr->GetActiveEditor());
    if (ed) {
        control = ed->GetControl();

        hasSelection = control->GetSelectionStart() != control->GetSelectionEnd();
        int wordStart = control->WordStartPosition(control->GetCurrentPos(), true);
        int wordEnd = control->WordEndPosition(control->GetCurrentPos(), true);
        wxString wordAtCursor = control->GetTextRange(wordStart, wordEnd);
        phraseAtCursor = control->GetSelectedText();
        // if selected text is part of a single line, don't suggest "search in selection"
        // 如果所选文本是单行的一部分,则不建议"在所选内容中搜索"
        if (control->LineFromPosition(control->GetSelectionStart())
            == control->LineFromPosition(control->GetSelectionEnd())) { hasSelection = false; }

        if (phraseAtCursor.IsEmpty()) { phraseAtCursor = wordAtCursor; }

        int selstartline = control->LineFromPosition(control->GetSelectionStart());
        int selendline = control->LineFromPosition(control->GetSelectionEnd());
        // the selection of several lines is not proposed as search pattern
        // 不建议选择多行作为搜索模式
        if (selstartline != selendline) { phraseAtCursor = wxEmptyString; }

    }

    FindReplaceBase *dlg = new FindReplaceDlg(Manager::Get()->GetAppWindow(), phraseAtCursor, hasSelection,
                                              !replace, !ed, explicitly_find_in_files);

    PlaceWindow(dlg);
    if (dlg->ShowModal() == wxID_CANCEL) {
        dlg->Destroy();
        return -2;
    }

    // Don't look for empty strings:
    // 不要寻找空字符串:
    if (dlg->GetFindString().empty()) {
        dlg->Destroy();
        cbMessageBox(wxT("无法查找空的搜索条件!"), wxT("错误"), wxOK | wxICON_EXCLAMATION, Manager::Get()->GetAppWindow());
        return -2;
    }

    if (!m_LastFindReplaceData) { m_LastFindReplaceData = new cbFindReplaceData; }

    m_LastFindReplaceData->start = 0;
    m_LastFindReplaceData->end = 0;
    m_LastFindReplaceData->findText = dlg->GetFindString();
    m_LastFindReplaceData->replaceText = dlg->GetReplaceString();
    m_LastFindReplaceData->eolMode = wxSCI_EOL_LF;
    m_LastFindReplaceData->multiLine = dlg->GetMultiLine();
    m_LastFindReplaceData->fixEOLs = dlg->GetFixEOLs();
    m_LastFindReplaceData->startFile = dlg->GetStartFile();

    m_LastFindReplaceData->findInFiles = dlg->IsFindInFiles();
    if (!m_LastFindReplaceData->findInFiles) {
        //AutoWrapSearch does not exist in FindInFiles dialog
        // "FindInFiles"对话框中不存在AutoWrapSearch
        m_LastFindReplaceData->autoWrapSearch = dlg->GetAutoWrapSearch();

        //FindUsesSelectedText does not exist in Replace dialogs
        //替换对话框中不存在FindUseSesselectedText
        if (!replace) { m_LastFindReplaceData->findUsesSelectedText = dlg->GetFindUsesSelectedText(); }
    }
    m_LastFindReplaceData->delOldSearches = dlg->GetDeleteOldSearches();
    m_LastFindReplaceData->sortSearchResult = dlg->GetSortSearchResult();
    m_LastFindReplaceData->matchWord = dlg->GetMatchWord();
    m_LastFindReplaceData->startWord = dlg->GetStartWord();
    m_LastFindReplaceData->matchCase = dlg->GetMatchCase();
    m_LastFindReplaceData->regEx = dlg->GetRegEx();
    m_LastFindReplaceData->directionDown = dlg->GetDirection() == 1;
    m_LastFindReplaceData->originEntireScope = dlg->GetOrigin() == 1;
    m_LastFindReplaceData->scope = dlg->GetScope();
    m_LastFindReplaceData->searchPath = dlg->GetSearchPath();
    m_LastFindReplaceData->searchMask = dlg->GetSearchMask();
    m_LastFindReplaceData->recursiveSearch = dlg->GetRecursive();
    m_LastFindReplaceData->searchProject = dlg->GetProject();
    m_LastFindReplaceData->searchTarget = dlg->GetTarget();
    m_LastFindReplaceData->hiddenSearch = dlg->GetHidden();
    m_LastFindReplaceData->initialreplacing = false;
    m_LastFindReplaceData->NewSearch = true;

    if (control) {   // if editor : store the selection start/end
        // only use this in case of !findInFiles and scope==1 (search in selection)
        //
        // 如果编辑器:存储选择开始/结束仅在这种情况下使用!findInFiles和scope==1(在所选内容中搜索)
        m_LastFindReplaceData->SearchInSelectionStart = control->GetSelectionStart();
        m_LastFindReplaceData->SearchInSelectionEnd = control->GetSelectionEnd();
    }
    dlg->Destroy();

    if (m_LastFindReplaceData->regEx) {
        // Match nasty regexes
        // 匹配讨厌的正则表达式
        if (m_LastFindReplaceData->findText.IsSameAs('^') || m_LastFindReplaceData->findText.IsSameAs('$')) {
            cbMessageBox(wxT("输入了错误的regex!请更正regex并重试!"), wxT("错误!"), wxICON_ERROR);
            return 0;
        }
    }

    int ReturnValue = 0;
    if (!replace) {
        if (m_LastFindReplaceData->findInFiles) { ReturnValue = FindInFiles(m_LastFindReplaceData); }
        else { ReturnValue = Find(control, m_LastFindReplaceData); }
    } else {
        m_LastFindReplaceData->initialreplacing = true;

        if (m_LastFindReplaceData->findInFiles) { ReturnValue = ReplaceInFiles(m_LastFindReplaceData); }
        else { ReturnValue = Replace(control, m_LastFindReplaceData); }
    }
    // we have searched, so no longer new search
    // 我们已经搜索过了,所以不再进行新的搜索
    m_LastFindReplaceData->NewSearch = false;

    //Default back to find or replace in Editor
    // 默认返回到编辑器中查找或替换
    if (m_LastFindReplaceData->findInFiles) {
        m_LastFindReplaceData->findInFiles = false;
    }
    return ReturnValue;
}

void FindReplace::CalculateFindReplaceStartEnd(cbStyledTextCtrl* control, cbFindReplaceData* data, bool replace) {
    if (!control || !data) { return; }
    // Beginning-of-file needs the entire scope
    // 文件开头需要整个范围
    if (data->startFile) {
        int clen = control->GetLength();
        int slen = data->findText.Len();

        data->start = 0;
        data->end = std::min(slen, clen);
    } else if (!data->findInFiles) {
        // Find in current Editor
        // 在当前编辑器中查找
        int ssta = control->GetSelectionStart();
        int send = control->GetSelectionEnd();
        int cpos = control->GetCurrentPos();
        int clen = control->GetLength();

        // when the user initially had a selection, but then changed the scope
        // to entire scope, the values of ssta and send will have a bad influence in
        // the following calculations, therefor check for the scenario
        // and set the ssta en send to cpos (in the case there would be no selection
        // that's the value they have [no selection : ssta=send=cpos])
        // only do this when it's a new search (when the search is continued (F3/Shift-F3)
        // there can be a selection, the last found match)
        //
        // 当用户最初有一个选择,但随后将作用域更改为整个作用域时,
        // ssta和send的值将对以下计算产生不良影响,因此请检查该方案并将ssta en send设置为cpos(如果没有选择,则为其值[没有选择:ssta=send=cpos]),
        // 仅执行此操作当它是一个新的搜索(当搜索继续时(F3/Shift-F3),可以有一个选择,最后找到的匹配项)

        if ((data->scope == 0) && data->NewSearch && (ssta != cpos || send != cpos)) {
            // Don't do this in replace mode, because we want to start the replacement
            // with the current selection, not the first match after the selection.
            //
            // 不要在替换模式下执行此操作,因为我们希望用当前选择开始替换,而不是选择后的第一个匹配.

            if (!replace) {
                ssta = cpos;
                send = cpos;
            }
        }

        data->start = 0;
        data->end = clen;

        // from pos or next/prev search
        if (!data->originEntireScope || !data->NewSearch) {
            // up
            if (!data->directionDown) {
                // initial replacing mode - include selection end : normal mode - skip until selection start
                // 初始替换模式-包括选择结束:正常模式-跳过直到选择开始
                data->start = (data->initialreplacing) ? std::max(send, cpos) : std::min(ssta, cpos);
            } else {
                // down
                // initial replacing mode - include selection start : normal mode - skip until selection end
                // 初始替换模式-包括选择开始:正常模式-跳过直到选择结束
                data->start = (data->initialreplacing) ? std::min(ssta, cpos) : std::max(send, cpos);
            }
        } else {
            // entire scope
            if (!data->directionDown) {
                // up
                data->start = clen;
            }
        }

        if (!data->directionDown) {
            // up
            data->end = 0;
        }

        // selected text, if user has deslected since last, then change scope
        // 所选文本,如果用户自上次取消选择,则更改范围
        if (data->scope == 1 &&
            control->GetSelectionStart() == control->GetSelectionEnd()) {
            // global
            data->scope = 0;
        }

        // selected text
        // 选定文本
        if (data->scope == 1) {
            if (data->NewSearch) {
                if (!data->directionDown) {
                    // up
                    data->start = std::max(ssta, send);
                    data->end = std::min(ssta, send);
                } else {
                    // down
                    data->start = std::min(ssta, send);
                    data->end = std::max(ssta, send);
                }
            } else {
                // this is the result of a next/previous search
                // rebase depending on the cursor position
                // 这是下一个/上一个搜索重新定位的结果,具体取决于光标位置
                ssta = data->SearchInSelectionStart;
                send = data->SearchInSelectionEnd;
                if (cpos < ssta || cpos > send) {
                    // regular reset (this also provide some sort of wrap around) (other editors also did it like that)
                    // 常规重置(这也提供了某种包装)(其他编辑器也这样做)
                    data->start = ssta;
                    data->end = send;
                } else {
                    data->start = cpos;
                    data->end = (data->directionDown) ? send : ssta;
                }
            }
        }
    } else {
        // FindInFiles
        // searching direction down, entire scope
        //Replace needs the entire scope, while find can wrap around.
        // 在文件中搜索(FindInFiles),向下搜索方向,整个范围,替换需要整个作用域,而查找可以环绕.
        data->start = (replace ? 0 : control->GetCurrentPos());
        data->end = control->GetLength();
    }
}

int FindReplace::Replace(cbStyledTextCtrl* control, cbFindReplaceData* data) {
    if (!control || !data) { return -1; }

    if (control->GetReadOnly()) {
        cbMessageBox(wxT("此文件是只读的.无法在只读文件中重新放置."),
                     wxT("警告"), wxICON_EXCLAMATION);
        return -1;
    }

    bool advRegex = false;
    bool advRegexNewLinePolicy = !data->IsMultiLine();
    int replacecount = 0;
    int foundcount = 0;
    int flags = 0;

    {
        int eolMode = control->GetEOLMode();
        // Convert our S&R data to the file's EOL mode.
        // 将我们的S&R数据转换为文件的EOL模式.
        data->ConvertEOLs(eolMode);
        if (data->IsMultiLine() && data->fixEOLs) {
            // First we must ensure that the file has consistent line endings.
            // As all the file's lines are affected, we disable change history for this step.
            //
            // 首先,我们必须确保文件具有一致的行尾.
            // 由于所有文件行都受到影响,因此我们禁用此步骤的更改历史记录.

            control->BeginUndoAction();
            control->SetChangeCollection(false);
            control->ConvertEOLs(eolMode);
            control->SetChangeCollection(true);
            control->EndUndoAction();
        }
    }

    // The undo is set at this point in case we need to convert the EOLs.
    // 撤销设置在这一点上,以防我们需要转换下线(EOLs).
    control->BeginUndoAction();
    CalculateFindReplaceStartEnd(control, data, true);

    if (data->matchWord) { flags |= wxSCI_FIND_WHOLEWORD; }
    if (data->startWord) { flags |= wxSCI_FIND_WORDSTART; }
    if (data->matchCase) { flags |= wxSCI_FIND_MATCHCASE; }
    if (data->regEx) {
        flags |= wxSCI_FIND_REGEXP;
        if (Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/use_posix_style_regexes"), false)) { flags |= wxSCI_FIND_POSIX; }
#ifdef wxHAS_REGEX_ADVANCED
        advRegex = Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/use_advanced_regexes"), false);
#endif
    }

    wxRegEx re;
#ifdef wxHAS_REGEX_ADVANCED
    if (advRegex) {
        if (data->matchCase) { re.Compile(data->findText, wxRE_ADVANCED | (wxRE_NEWLINE * advRegexNewLinePolicy)); }
        else { re.Compile(data->findText, wxRE_ADVANCED | (wxRE_NEWLINE * advRegexNewLinePolicy) | wxRE_ICASE); }
    }
#endif

    int pos = -1;
    bool replace = false;
    bool confirm = true;
    bool stop = false;
    wxPoint LastDlgPosition;
    bool HaveLastDlgPosition = false;
    bool wrapAround = false;
    int data_start_initial = data->start;
    bool wrapAroundNotification = false;

    while (!stop) {
        int lengthFound = 0;
        if (!advRegex) { pos = control->FindText(data->start, data->end, data->findText, flags, &lengthFound); }
        else {
            wxString text = control->GetTextRange(data->start, data->end);
            if (re.Matches(text)) {
                size_t start, len;
                re.GetMatch(&start, &len, 0);
                pos = start + data->start;
                lengthFound = len;
                //For searches for "^" or "$" (and null returning variants on this) need to make sure we have forward progress and not simply matching on a previous BOL/EOL find
                //对于"^"或"$"(以及null在此返回变量)的搜索,需要确保我们有向前的进度,而不是简单地匹配以前的BOL/EOL查找
                if ((start == 0) && (len == 0)) {
                    text = text.Mid(1);
                    if (re.Matches(text)) {
                        re.GetMatch(&start, &len, 0);
                        pos = start + data->start + 1;
                        lengthFound = len;
                    } else { pos = -1; }
                }
            } else { pos = -1; }
        }

        // Not found at the beginning of file
        // 在文件开头找不到
        if (data->startFile && pos > 0) { pos = -1; }

        if (pos != -1 && data->start != data->end) {
            control->GotoPos(pos);
            control->EnsureVisible(control->LineFromPosition(pos));
            control->SetSelectionVoid(pos, pos + lengthFound);
            data->start = pos;
            // special treatment only necessary the first time
            // 只有第一次才需要特殊处理
            data->initialreplacing = false;
        } else if (!wrapAround) {
            // scope = global text
            // 范围等于全局文本
            if ((data->scope == 0) &&
                ((data->directionDown && data_start_initial != 0) ||
                 (!data->directionDown && data_start_initial != control->GetLength()))) {
                wxString msg;
                if (data->directionDown) { msg = wxT("找不到文本,是否从文档开始搜索?"); }
                else { msg = wxT("找不到文本,是否从文档结束搜索?"); }

                bool auto_wrap_around = data->autoWrapSearch;
                if (auto_wrap_around) { wrapAroundNotification = true; }
                if (auto_wrap_around || cbMessageBox(msg, wxT("结果"), wxOK | wxCANCEL | wxICON_QUESTION) == wxID_OK) {
                    data->end = data_start_initial;
                    data->start = (data->directionDown) ? 0 : control->GetLength();
                    // signal the wrap-around
                    // 发出环绕信号
                    wrapAround = true;
                    continue;
                } else {
                    // done - user doesn't want to wrap around
                    // 完成-用户不想环绕
                    break;
                }
            } else {
                // done - we're replacing in a selection of text
                // 完成-我们正在替换选定的文本
                break;
            }
        } else {
            // done - already wrapped around once
            // 完成-已经绕了一圈
            break;
        }

        if (wrapAroundNotification) {
            wxBell();
            InfoWindow::Display(wxT("查找操作"), wxT("已到达文档结尾"), 1000);
            wrapAroundNotification = false;
        }

        foundcount++;

        if (confirm) {
            ConfirmReplaceDlg dlg(Manager::Get()->GetAppWindow());
            // dlg.CalcPosition(control);
            // TODO (thomas#1#): Check whether the existing code actually works with twin view
            // else, we need something like:
            // PlaceWindow(&dlg, pdlRelative);
            //
            // 待处理(thomas#1#):检查现有代码是否与twin视图一起工作,否则我们需要类似的东西：PlaceWindow(&dlg, pdlRelative);

            // NOTE (Tiwag#1#): dlg.CalcPosition doesn't work for me with dual monitor setup,
            //     workaround : remember last dialog position, user can position
            //                  it outside of text where he wants
            // Move dialog to last position if already available,
            // else place it according to environments settings
            //
            // 注(Tiwag#1#):dlg.CalcPosition不适合我使用双监视器设置,解决方法:记住最后一个对话框位置,用户可以将其放置在文本之外他想要的位置.
            // 将对话框移动到最后一个位置(如果已可用),否则根据环境设置放置它.

            if (HaveLastDlgPosition) { dlg.Move(LastDlgPosition); }
            else { dlg.CalcPosition(control); }

            int ans = dlg.ShowModal();
            LastDlgPosition = dlg.GetPosition();
            HaveLastDlgPosition = true;
            switch (ans) {
                case crYes:
                    replace = true;
                    break;
                case crNo:
                    replace = false;
                    break;
                case crAll:
                    replace = true;
                    confirm = false;
                    control->Freeze();
                    break;
                case crCancel:
                    stop = true;
                    break;
                default:
                    break;
            }
        }

        if (!stop) {
            if (replace) {
                int lengthReplace = data->replaceText.Length();
                replacecount++;
                if (data->regEx) {
                    // set target same as selection
                    // 设置与所选内容相同的目标
                    control->SetTargetStart(control->GetSelectionStart());
                    control->SetTargetEnd(control->GetSelectionEnd());
                    if (advRegex) {
                        wxString text = control->GetSelectedText();
                        re.Replace(&text, data->replaceText, 1);
                        lengthReplace = text.Len();
                        control->ReplaceSelection(text);
                    } else {
                        // replace with regEx support
                        // 替换为regEx支持
                        lengthReplace = control->ReplaceTargetRE(data->replaceText);
                    }

                    // reset target
                    // 重置目标
                    control->SetTargetStart(0);
                    control->SetTargetEnd(0);
                } else { control->ReplaceSelection(data->replaceText); }
                if (data->directionDown) { data->start += lengthReplace; }

                // adjust end pos by adding the length difference between find and replace strings
                // 通过添加查找和替换字符串之间的长度差来调整结束位置
                int diff = lengthReplace - lengthFound;
                if (data->directionDown) { data->end += diff; }
                else {
                    if (data->end < diff) { data->end = 0; }
                    else { data->end -= diff; }
                }
            } else {
                if (data->directionDown) { data->start += lengthFound; }
                else { data->start -= lengthFound; }
            }
        }
    }
    if (control->IsFrozen()) { control->Thaw(); }
    control->EndUndoAction();
    wxString msg;
    if (foundcount == 0) { msg = wxString::Format(wxT("找不到'%s'的匹配项 "), data->findText.wx_str()); }
    else if (replacecount == 0 && foundcount == 1) { msg = wxT("找到一个匹配项,但未替换"); }
    else { msg.Printf(wxT("替换了%i个匹配项(共%i个匹配项)"), replacecount, foundcount); }
    cbMessageBox(msg, wxT("结果"), wxICON_INFORMATION);
    control->SetSCIFocus(true);

    return pos;
}

int FindReplace::ReplaceInFiles(cbFindReplaceData* data) {
    if (!data) return 0;
    if (data->findText.IsEmpty()) return 0;

    EditorManager *editorMgr = Manager::Get()->GetEditorManager();

    bool IsMultiLine = data->IsMultiLine();

    // let's make a list of all the files to search in
    // 让我们列一个要搜索的所有文件的列表
    wxArrayString filesList;
    // find in open files
    // 在打开的文件中查找
    if (data->scope == 0) {
        // fill the search list with the open files
        // 用打开的文件填充搜索列表
        for (int i = 0; i < editorMgr->GetEditorsCount(); ++i) {
            cbEditor *ed = editorMgr->GetBuiltinEditor(i);
            if (ed) { filesList.Add(ed->GetFilename()); }
        }
    } else if (data->scope == 1) {
        // find in project files
        // fill the search list with all the project files
        // 在项目文件中查找
        // 用所有项目文件填充搜索列表
        if (data->searchProject < 0) {
            cbMessageBox(wxT("没有要搜索的项目!"), wxT("错误"), wxICON_WARNING);
            return 0;
        }
        cbProject *prj = (*Manager::Get()->GetProjectManager()->GetProjects())[data->searchProject];
        wxString target;
        wxString fullpath = _T("");
        if (data->searchTarget >= 0) { target = prj->GetBuildTarget(data->searchTarget)->GetTitle(); }
        for (FilesList::iterator it = prj->GetFilesList().begin(); it != prj->GetFilesList().end(); ++it) {
            ProjectFile *pf = *it;
            if (pf) {
                if (target != wxEmptyString && pf->buildTargets.Index(target) < 0) { continue; }
                fullpath = pf->file.GetFullPath();
                // avoid adding duplicates
                // 避免添加重复项
                if (filesList.Index(fullpath) >= 0) { continue; }
                // Does the file exist?
                // 文件是否存在?
                if (wxFileExists(fullpath)) { filesList.Add(fullpath); }
            }
        }
    } else if (data->scope == 2) {
        // find in workspace
        // loop over all the projects in the workspace (they are contained in the ProjectManager)
        // 在工作区中查找
        // 循环工作区中的所有项目(它们包含在ProjectManager中)

        const ProjectsArray *pProjects = Manager::Get()->GetProjectManager()->GetProjects();
        if (pProjects) {
            int count = pProjects->GetCount();
            for (int idxProject = 0; idxProject < count; ++idxProject) {
                cbProject *pProject = pProjects->Item(idxProject);
                if (pProject) {
                    wxString fullpath = _T("");

                    for (FilesList::iterator it = pProject->GetFilesList().begin(); it != pProject->GetFilesList().end(); ++it) {
                        ProjectFile *pf = *it;
                        if (pf) {
                            fullpath = pf->file.GetFullPath();
                            // avoid adding duplicates
                            if (filesList.Index(fullpath) == -1) {
                                // Does the file exist?
                                if (wxFileExists(fullpath)) { filesList.Add(fullpath); }
                            }
                        }
                    } // end for : idx : idxFile
                }
            } // end for : idx : idxProject
        }
    } else if (data->scope == 3) {
        // replace in custom search path and mask
        // fill the search list with the files found under the search path
        // 替换为自定义搜索路径并用在搜索路径下找到的文件填充搜索列表
        int flags = wxDIR_FILES |
                    (data->recursiveSearch ? wxDIR_DIRS : 0) |
                    (data->hiddenSearch ? wxDIR_HIDDEN : 0);
        wxArrayString masks = GetArrayFromString(data->searchMask);
        if (!masks.GetCount()) { masks.Add(_T("*")); }
        unsigned int count = masks.GetCount();
        // 没有日志记录
        wxLogNull ln;
        for (unsigned int i = 0; i < count; ++i) {
            // wxDir::GetAllFiles() does *not* clear the array, so it suits us just fine ;)
            // wxDir::GetAllFiles()不清除阵列,所以很适合我们
            wxDir::GetAllFiles(data->searchPath, &filesList, masks[i], flags);
        }
    }
    // if the list is empty, leave
    // 如果列表为空,请离开
    int filesCount = filesList.GetCount();
    if (filesCount == 0) {
        cbMessageBox(wxT("没有要搜索的文件!"), wxT("错误"), wxICON_WARNING);
        return 0;
    }

    bool advRegex = false;
    bool advRegexNewLinePolicy = !data->IsMultiLine();
    int flags = 0;
    if (data->matchWord) { flags |= wxSCI_FIND_WHOLEWORD; }
    if (data->startWord) { flags |= wxSCI_FIND_WORDSTART; }
    if (data->matchCase) { flags |= wxSCI_FIND_MATCHCASE; }
    if (data->regEx) {
        flags |= wxSCI_FIND_REGEXP;
        if (Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/use_posix_style_regexes"), false)) { flags |= wxSCI_FIND_POSIX; }
#ifdef wxHAS_REGEX_ADVANCED
        advRegex = Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/use_advanced_regexes"), false);
#endif
    }

    wxRegEx re;
#ifdef wxHAS_REGEX_ADVANCED
    if (advRegex) {
        if (data->matchCase) { re.Compile(data->findText, wxRE_ADVANCED | (wxRE_NEWLINE * advRegexNewLinePolicy)); }
        else { re.Compile(data->findText, wxRE_ADVANCED | (wxRE_NEWLINE * advRegexNewLinePolicy) | wxRE_ICASE); }
    }
#endif


    bool replace = false;
    bool confirm = true;
    bool stop = false;
    bool wholeFile = false;
    bool all = false;
    int pos = -1;

    wxPoint LastDlgPosition;
    bool HaveLastDlgPosition = false;

    wxProgressDialog *progress = nullptr;
    wxString fileContents;
    wxString enc_name = Manager::Get()->GetConfigManager(_T("editor"))->Read(_T("/default_encoding"), wxLocale::GetSystemEncodingName());
    wxFontEncoding def_encoding = wxFontMapper::GetEncodingFromName(enc_name);

    // keep a copy of the find struct
    // 保留find结构的副本
    cbFindReplaceData dataCopy = *data;

    int read_only_files_skipped = 0;
    for (int i = 0; i < filesCount && !stop; ++i) {
        cbEditor *ed = NULL;
        cbStyledTextCtrl *control = NULL;
        bool fileWasNotOpen = false;

        if (progress) {
            if (!progress->Update(i)) {
                if (cbMessageBox(wxT("是否确实要停止在文件中替换?"), wxT("确认"), wxICON_QUESTION | wxYES_NO) == wxID_YES) { break; }
                else { progress->Resume(); }
            }
        }

        //Check if this file is already open
        // 检查此文件是否已打开
        EditorBase *eb = editorMgr->IsOpen(filesList[i]);
        if (eb) {
            //File was already open
            // 文件已打开
            fileWasNotOpen = false;

            ed = editorMgr->GetBuiltinEditor(eb);
            if (ed) { control = ed->GetControl(); }
        }

        //If it's still NULL, open a new editor
        // 如果仍然为空,请打开一个新编辑器
        if (!control) {
            wxFile file(filesList[i]);
            if (!file.IsOpened()) { continue; }
            fileContents = cbReadFileContents(file, def_encoding);
            if (advRegex) {
                if (!re.Matches(fileContents)) { continue; }
            } else {
                int pos_str;
                if (!data->matchCase) { pos_str = fileContents.Upper().Find(data->findText.Upper()); }
                else { pos_str = fileContents.Find(data->findText); }
                if (pos_str == -1) { continue; }
                //TODO: handling to skip file if data->matchWord or data->startWord are set
                //TODO:如果设置了data->matchWord或data->startWord,则跳过文件的处理
            }

            //File was not open, i opened it.
            // 文件没有打开,我打开了它.
            fileWasNotOpen = true;

            ed = editorMgr->Open(filesList[i]);
            if (ed) { control = ed->GetControl(); }
        }
        //Still NULL?
        // 还是NULL?
        if (!control || !ed) { continue; }

        if (control->GetReadOnly()) {
            read_only_files_skipped++;
            continue;
        }

        editorMgr->SetActiveEditor(ed);

        *data = dataCopy;

        bool replacementsWereMade = false;
        // If we fix the file's EOLs for multi-line S&R, we're actually modifying it.
        // What we really want to know is whether we actually did a replacement.
        // If not (and the file was not open in the first place), we can safely close the file.

        // 如果我们为多行s&R修复文件的EOLs,我们实际上是在修改它.
        // 我们真正想知道的是我们是否真的做了替换.
        // 如果没有(而且文件一开始没有打开),我们可以安全地关闭文件.

        {
            // We should be checking if the data has EOLs before converting them. But searching is
            // just as expensive as doing the conversion itself, so we just convert.
            //
            // 在转换数据之前,我们应该先检查数据是否有下线.但是搜索和转换本身一样昂贵,所以我们只是转换.

            int eolMode = control->GetEOLMode();
            data->ConvertEOLs(eolMode);

            if (IsMultiLine && data->fixEOLs) {
                control->BeginUndoAction(); //undo
                control->SetChangeCollection(false);
                control->ConvertEOLs(eolMode);
                control->SetChangeCollection(true);
                control->EndUndoAction();
            }
        }

        control->BeginUndoAction(); // undo
        CalculateFindReplaceStartEnd(control, data, true);

        //reset bools
        wholeFile = false;
        if (!all) { confirm = true; }

        //Replace in this file
        // 在这个文件里替换
        while (!stop || wholeFile) {
            int lengthFound = 0;
            if (!advRegex) { pos = control->FindText(data->start, data->end, data->findText, flags, &lengthFound); }
            else {
                wxString text = control->GetTextRange(data->start, data->end);
                if (re.Matches(text)) {
                    size_t start, len;
                    re.GetMatch(&start, &len, 0);
                    pos = start + data->start;
                    lengthFound = len;
                    //For searches for "^" or "$" (and null returning variants on this) need to make sure we have forward progress and not simply matching on a previous BOL/EOL find
                    //对于“^”或“$”(和返回此变量的null)的搜索,需要确保我们有前进的进度,而不是简单地匹配以前的BOL/EOL查找
                    if ((start == 0) && (len == 0)) {
                        text = text.Mid(1);
                        if (re.Matches(text)) {
                            re.GetMatch(&start, &len, 0);
                            pos = start + data->start + 1;
                            lengthFound = len;
                        } else
                            pos = -1;
                    }
                } else
                    pos = -1;
            }

            // Not found at the beginning of file
            // 在文件开头找不到
            if (data->startFile && (pos > 0)) { pos = -1; }

            if ((pos == -1) || (data->start == data->end)) { break; }

            if (confirm) {
                control->GotoPos(pos);
                control->EnsureVisible(control->LineFromPosition(pos));
            }
            control->SetSelectionVoid(pos, pos + lengthFound);
            data->start = pos;
            // special treatment only necessary the first time
            // 只有第一次才需要特殊处理
            data->initialreplacing = false;

            if (confirm) {
                ConfirmReplaceDlg dlg(Manager::Get()->GetAppWindow(), true);
                // dlg.CalcPosition(control);
                // TODO (thomas#1#): Check whether the existing code actually works with twin view
                // else, we need something like:
                // PlaceWindow(&dlg, pdlRelative);

                // NOTE (Tiwag#1#): dlg.CalcPosition doesn't work for me with dual monitor setup,
                //     workaround : remember last dialog position, user can position
                //                  it outside of text where he wants
                // Move dialog to last position if already available,
                // else place it according to environments settings
                if (HaveLastDlgPosition) { dlg.Move(LastDlgPosition); }
                else { dlg.CalcPosition(control); }

                int ans = dlg.ShowModal();
                LastDlgPosition = dlg.GetPosition();
                HaveLastDlgPosition = true;
                switch (ans) {
                    case crYes:
                        replace = true;
                        break;
                    case crNo:
                        replace = false;
                        break;
                    case crAllInFile:
                        confirm = false;
                        replace = true;
                        wholeFile = true;
                        break;
                    case crSkipFile:
                        confirm = false;
                        replace = false;
                        wholeFile = true;
                        break;
                    case crAll:
                        replace = true;
                        confirm = false;
                        all = true;
                        // let's create a progress dialog because it might take some time depending on the files count
                        // 让我们创建一个进度对话框,因为它可能需要一些时间,这取决于文件计数
                        progress = new wxProgressDialog(wxT("在文件中替换"),
                                                        wxT("正在替换文件,请稍候..."),
                                                        filesCount,
                                                        Manager::Get()->GetAppWindow(),
                                                        wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
                        PlaceWindow(progress);
                        // now that we need no confirmation, freeze the app window
                        // 现在我们不需要确认,冻结应用程序窗口
                        Manager::Get()->GetAppWindow()->Freeze();
                        break;
                    case crCancel:
                        stop = true;
                        break;
                    default:
                        break;
                }
            }// if

            if (!stop) {
                if (replace) {
                    replacementsWereMade = true;
                    int lengthReplace = data->replaceText.Length();
                    if (data->regEx) {
                        // set target same as selection
                        control->SetTargetStart(control->GetSelectionStart());
                        control->SetTargetEnd(control->GetSelectionEnd());
                        if (advRegex) {
                            wxString text = control->GetSelectedText();
                            re.Replace(&text, data->replaceText, 1);
                            lengthReplace = text.Len();
                            control->ReplaceSelection(text);
                        } else // replace with regEx support
                            lengthReplace = control->ReplaceTargetRE(data->replaceText);

                        // reset target
                        control->SetTargetStart(0);
                        control->SetTargetEnd(0);
                    } else { control->ReplaceSelection(data->replaceText); }

                    data->start += lengthReplace;

                    // adjust end pos by adding the length difference
                    // between find and replace strings
                    // 通过添加查找和替换字符串之间的长度差来调整结束位置
                    int diff = lengthReplace - lengthFound;
                    if (data->directionDown) { data->end += diff; }
                    else { data->end -= diff; }
                } else {
                    if (data->directionDown) { data->start += lengthFound; }
                    else { data->start -= lengthFound; }
                }
            }// if
        }// while

        control->EndUndoAction(); // undo

        // If opened the file and no replacement was made, close the editor
        // 如果打开文件但没有进行替换,请关闭编辑器
        if (!replacementsWereMade && fileWasNotOpen) { editorMgr->Close(ed, true); }
    }// for

    if (read_only_files_skipped) {
        wxString msg;
        msg.Printf(wxT("已跳过%d个只读文件."), read_only_files_skipped);
        InfoWindow::Display(wxT("警告"), msg);
    }

    // if we showed the progress, the app window is frozen; unfreeze it
    // 如果我们显示了进度,应用程序窗口将被冻结;将其解冻
    if (progress) { Manager::Get()->GetAppWindow()->Thaw(); }

    delete progress;
    AnnoyingDialog dlg(wxT("在文件中替换"),
                       wxT("文件替换已完成所有操作."),
                       wxART_INFORMATION, AnnoyingDialog::OK);
    dlg.ShowModal();

    return pos;
}

int FindReplace::Find(cbStyledTextCtrl* control, cbFindReplaceData* data) {
    if (!control || !data) { return -1; }

    bool advRegex = false;
    bool advRegexNewLinePolicy = !data->IsMultiLine();
    int flags = 0;
    data->ConvertEOLs(control->GetEOLMode());
    CalculateFindReplaceStartEnd(control, data);

    if (data->matchWord)
        flags |= wxSCI_FIND_WHOLEWORD;
    if (data->startWord)
        flags |= wxSCI_FIND_WORDSTART;
    if (data->matchCase)
        flags |= wxSCI_FIND_MATCHCASE;
    if (data->regEx) {
        flags |= wxSCI_FIND_REGEXP;
        if (Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/use_posix_style_regexes"), false)) { flags |= wxSCI_FIND_POSIX; }
#ifdef wxHAS_REGEX_ADVANCED
        advRegex = Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/use_advanced_regexes"), false);
#endif
    }

    wxRegEx re;
#ifdef wxHAS_REGEX_ADVANCED
    if (advRegex) {
        if (data->matchCase) { re.Compile(data->findText, wxRE_ADVANCED | (wxRE_NEWLINE * advRegexNewLinePolicy)); }
        else { re.Compile(data->findText, wxRE_ADVANCED | (wxRE_NEWLINE * advRegexNewLinePolicy) | wxRE_ICASE); }
    }
#endif

    int pos = -1;
    // avoid infinite loop when wrapping search around, eventually crashing WinLogon O.O
    // 在包装搜索时避免无限循环,最终导致WinLogon O.O崩溃
    bool wrapAround = false;
    int StartPos = 0;
    int EndPos = control->GetLength();
    if (data->scope == 1) // selectd text
    {
        StartPos = data->SearchInSelectionStart;
        EndPos = data->SearchInSelectionEnd;
    }
    bool wrapAroundNotification = false;
    // loop while not found and user selects to start again from the top
    // 未找到时循环,用户选择从顶部重新开始
    while (true) {
        int lengthFound = 0;
        if (!advRegex) { pos = control->FindText(data->start, data->end, data->findText, flags, &lengthFound); }
        else {
            wxString text = control->GetTextRange(data->start, data->end);
            if (re.Matches(text)) {
                size_t start, len;
                re.GetMatch(&start, &len, 0);
                pos = start + data->start;
                lengthFound = len;
                //For searches for "^" or "$" (and null returning variants on this) need to make sure we have forward progress and not simply matching on a previous BOL/EOL find
                //对于"^"或"$"(和返回此变量的null)的搜索,需要确保我们有前进的进度,而不是简单地匹配以前的BOL/EOL查找
                if ((start == 0) && (len == 0)) {
                    text = text.Mid(1);
                    if (re.Matches(text)) {
                        re.GetMatch(&start, &len, 0);
                        pos = start + data->start + 1;
                        lengthFound = len;
                    } else { pos = -1; }
                }
            } else { pos = -1; }
        }
        if (pos != -1 && data->start != data->end) {
            int line = control->LineFromPosition(pos);
            int onScreen = control->LinesOnScreen() >> 1;
            int l1 = line - onScreen;
            int l2 = line + onScreen;
            // unfold visible lines on screen
            // 在屏幕上展开可见线
            for (int l = l1; l <= l2; l += 2) { control->EnsureVisible(l); }
            // center selection on screen
            // 在屏幕上居中选择
            control->GotoLine(l1);
            control->GotoLine(l2);
            control->GotoLine(line);
            control->SetSelectionVoid(pos, pos + lengthFound);
//            Manager::Get()->GetLogManager()->DebugLog("pos=%d, selLen=%d, length=%d", pos, data->end - data->start, lengthFound);
            data->start = pos;
            // done
            // 完成
            break;
        } else if (!wrapAround && !data->findInFiles) {
            // for "find in files" we don't want to show messages
            // 对于"在文件中查找",我们不想显示消息
            if ((data->directionDown && data->start != StartPos)
                || (!data->directionDown && data->start != EndPos)) {
                wxString msg;
                // selected text
                // 选定文本
                if (data->scope == 1) {
                    if (data->directionDown) { msg = wxT("找不到文本,是否从所选内容的开头搜索?"); }
                    else { msg = wxT("找不到文本,是否从所选内容的结尾搜索?"); }
                } else {
                    if (data->directionDown) { msg = wxT("找不到文本,是否从所选内容的开头搜索?"); }
                    else { msg = wxT("找不到文本,是否从所选内容的结尾搜索?"); }
                }

                bool auto_wrap_around = data->autoWrapSearch;
                if (auto_wrap_around)
                    wrapAroundNotification = true;

                if (auto_wrap_around || cbMessageBox(msg, wxT("结果"), wxOK | wxCANCEL | wxICON_QUESTION) == wxID_OK) {
                    // signal the wrap-around
                    wrapAround = true;
                    // selected text
                    if (data->scope == 1) {
                        if (data->directionDown) {
                            data->start = data->SearchInSelectionStart;
                            data->end = data->SearchInSelectionEnd;
                        } else {
                            data->start = data->SearchInSelectionEnd;
                            data->end = data->SearchInSelectionStart;
                        }
                    } else {
                        // global
                        if (data->directionDown) {
                            data->start = 0;
                            data->end = control->GetLength();
                        } else {
                            data->start = control->GetLength();
                            data->end = 0;
                        }
                    }
                } else { break; }
            } else {
                wxString msg;
                msg.Printf(wxT("未发现: %s"), data->findText.wx_str());
                cbMessageBox(msg, wxT("结果"), wxICON_INFORMATION);
                control->SetSCIFocus(true);
                wrapAroundNotification = false;
                break; // done
            }
        } else if (wrapAround) {
            wxString msg;
            msg.Printf(wxT("未发现: %s"), data->findText.wx_str());
            cbMessageBox(msg, wxT("结果"), wxICON_INFORMATION);
            wrapAroundNotification = false;
            break; // done
        } else { break; }
    }

    if (wrapAroundNotification) {
        wxBell();
        InfoWindow::Display(wxT("查找操作"), wxT("已到达文档结尾"), 1000);
    }

    return pos;
}

int FindReplace::FindInFiles(cbFindReplaceData* data) {
    if (!data || data->findText.IsEmpty()) { return 0; }

    // clear old search results
    // 清除旧的搜索结果
    if (data->delOldSearches) {
        m_pSearchLog->Clear();
    }
    int oldcount = m_pSearchLog->GetItemsCount();

    EditorManager *editorMgr = Manager::Get()->GetEditorManager();

    // let's make a list of all the files to search in
    // 让我们列一个要搜索的所有文件的列表
    wxArrayString filesList;
    // find in open files
    if (data->scope == 0) {
        // fill the search list with the open files
        // 用打开的文件填充搜索列表
        for (int i = 0; i < editorMgr->GetEditorsCount(); ++i) {
            cbEditor *ed = editorMgr->GetBuiltinEditor(i);
            if (ed) { filesList.Add(ed->GetFilename()); }
        }
    } else if (data->scope == 1) {
        // find in project files
        // fill the search list with all the project files
        if (data->searchProject < 0) {
            cbMessageBox(wxT("没有要搜索的项目!"), wxT("错误"), wxICON_WARNING);
            return 0;
        }
        cbProject *prj = (*Manager::Get()->GetProjectManager()->GetProjects())[data->searchProject];
        wxString target;
        wxString fullpath = _T("");
        if (data->searchTarget >= 0) { target = prj->GetBuildTarget(data->searchTarget)->GetTitle(); }
        for (FilesList::iterator it = prj->GetFilesList().begin(); it != prj->GetFilesList().end(); ++it) {
            ProjectFile *pf = *it;
            if (pf) {
                if (target != wxEmptyString && pf->buildTargets.Index(target) < 0) { continue; }
                fullpath = pf->file.GetFullPath();
                if (filesList.Index(fullpath) >= 0) // avoid adding duplicates
                { continue; }
                if (wxFileExists(fullpath))  // Does the file exist?
                { filesList.Add(fullpath); }
            }
        }
    } else if (data->scope == 2) {
        // find in workspace
        // loop over all the projects in the workspace (they are contained in the ProjectManager)
        const ProjectsArray *pProjects = Manager::Get()->GetProjectManager()->GetProjects();
        int count = 0;
        if (pProjects) { count = pProjects->GetCount(); }
        if (!count) {
            cbMessageBox(wxT("没有可搜索的工作区!"), wxT("错误"), wxICON_WARNING);
            return 0;
        }
        for (int idxProject = 0; idxProject < count; ++idxProject) {
            cbProject *pProject = pProjects->Item(idxProject);
            if (pProject) {
                wxString fullpath = _T("");
                for (FilesList::iterator it = pProject->GetFilesList().begin(); it != pProject->GetFilesList().end(); ++it) {
                    ProjectFile *pf = *it;
                    if (pf) {
                        fullpath = pf->file.GetFullPath();
                        // avoid adding duplicates
                        if (filesList.Index(fullpath) == -1) {
                            // Does the file exist?
                            if (wxFileExists(fullpath)) { filesList.Add(fullpath); }
                        }
                    }
                } // end for : idx : idxFile
            }
        } // end for : idx : idxProject
    } else if (data->scope == 3) {
        // find in custom search path and mask
        // fill the search list with the files found under the search path
        int flags = wxDIR_FILES |
                    (data->recursiveSearch ? wxDIR_DIRS : 0) |
                    (data->hiddenSearch ? wxDIR_HIDDEN : 0);
        wxArrayString masks = GetArrayFromString(data->searchMask);
        if (!masks.GetCount()) { masks.Add(_T("*")); }
        unsigned int count = masks.GetCount();

        for (unsigned int i = 0; i < count; ++i) {
            // wxDir::GetAllFiles() does *not* clear the array, so it suits us just fine ;)
            // wxDir::GetAllFiles()不清除阵列,所以很适合我们
            wxDir::GetAllFiles(data->searchPath, &filesList, masks[i], flags);
        }
    } else if (data->scope == 4) {
        // find in current file only
        cbEditor *ed = editorMgr->GetBuiltinActiveEditor();
        if (ed) { filesList.Add(ed->GetFilename()); }
    }


    // if the list is empty, leave
    if (filesList.GetCount() == 0) {
        cbMessageBox(wxT("没有要搜索的文件!"), wxT("错误"), wxICON_WARNING);
        return 0;
    }

    // sort search results alphabetically if option is on
    // 如果选项启用,则按字母顺序对搜索结果排序
    if (m_LastFindReplaceData->sortSearchResult) { filesList.Sort(); }

    // now that list is filled, we'll search
    // but first we'll create a hidden cbStyledTextCtrl to do the search for us ;)
    // 现在该列表已填充,我们将搜索,但首先我们将创建一个隐藏的cbStyledTextCtrl来搜索我们

    cbStyledTextCtrl *control = new cbStyledTextCtrl(editorMgr->GetNotebook(), -1, wxDefaultPosition, wxSize(0, 0));
    control->Show(false); //hidden

    // let's create a progress dialog because it might take some time depending on the files count
    // 让我们创建一个进度对话框,因为它可能需要一些时间,这取决于文件计数
    wxProgressDialog *progress = new wxProgressDialog(wxT("在文件中查找"),
                                                      wxT("正在搜索文件内部,请稍候..."),
                                                      filesList.GetCount(),
                                                      Manager::Get()->GetAppWindow(),
                                                      wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);

    PlaceWindow(progress);

    // keep a copy of the find struct
    cbFindReplaceData localData = *data;

    if (!data->delOldSearches) {
        LogSearch(_T("=========="), -1, _T("=== \"") + data->findText + _T("\" ==="));
        oldcount++;
    }

    int lastline = -1;
    int count = 0;
    for (size_t i = 0; i < filesList.GetCount(); ++i) {
        // update the progress bar
        // 更新进度条
        // user pressed "Cancel"
        // 用户按下“取消”
        if (!progress->Update(i)) { break; }

        // re-initialize the find struct for every file searched
        // 为搜索的每个文件重新初始化find结构
        *data = localData;

        // check if the file is already opened in built-in editor and do search in it
        // 检查文件是否已在内置编辑器中打开,并在其中进行搜索
        cbEditor *ed = editorMgr->IsBuiltinOpen(filesList[i]);
        if (ed) { control->SetText(ed->GetControl()->GetText()); }
        else {
            // else load the file in the control
            // 否则在控件中加载文件
            EncodingDetector detector(filesList[i]);
            if (!detector.IsOK()) {
                // failed
                // 失败
                continue;
            }
            control->SetText(detector.GetWxStr());
        }

        // now search for first occurence
        // 现在搜索第一个事件
        if (Find(control, data) == -1) {
            lastline = -1;
            continue;
        }

        int line = control->LineFromPosition(control->GetSelectionStart());
        lastline = line;

        // make the filename relative
        // 使文件名关联
        wxString filename = filesList[i];
        if (filename.StartsWith(data->searchPath)) {
            wxFileName fname(filename);
            fname.MakeRelativeTo(data->searchPath);
            filename = fname.GetFullPath();
        }

        // log it
        LogSearch(filename, line + 1, control->GetLine(line));
        ++count;

        // now loop finding the next occurence
        // 现在循环查找下一个遇到的(匹配项)
        while (FindNext(true, control, data, false) != -1) {
            // we already found it, so break and avoid possible infinite loop
            // 我们已经找到了,所以打破并避免可能的无限循环
            if (data->startFile) { break; }
//            // log it
            line = control->LineFromPosition(control->GetSelectionStart());
            // avoid multiple hits on the same line (try search for "manager")
            //避免在同一行出现多个点击(尝试搜索“manager”)
            if (line == lastline) { continue; }

            lastline = line;
            LogSearch(filename, line + 1, control->GetLine(line));
            ++count;
        }
    }
    // done with it
    //结束了
    delete control;
    // done here too
    // 也在这里做
    delete progress;

    if (count > 0) {
        m_pSearchLog->SetBasePath(data->searchPath);
        if (Manager::Get()->GetConfigManager(_T("message_manager"))->ReadBool(_T("/auto_show_search"), true)) {
            CodeBlocksLogEvent evtSwitch(cbEVT_SWITCH_TO_LOG_WINDOW, m_pSearchLog);
            CodeBlocksLogEvent evtShow(cbEVT_SHOW_LOG_MANAGER);

            Manager::Get()->ProcessEvent(evtSwitch);
            Manager::Get()->ProcessEvent(evtShow);
        }
        m_pSearchLog->FocusEntry(oldcount);
    } else {
        wxString msg;
        if (data->delOldSearches) {
            msg.Printf(wxT("没发现: %s"), data->findText.c_str());
            cbMessageBox(msg, wxT("结果"), wxICON_INFORMATION);
            cbEditor *ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
            if (ed) { ed->GetControl()->SetSCIFocus(true); }
        } else {
            msg.Printf(wxT("在%lu文件中找不到"), static_cast<unsigned long>(filesList.GetCount()));
            LogSearch(wxT(""), -1, msg);
            m_pSearchLog->FocusEntry(oldcount);
        }
    }

    return count;
}

int FindReplace::FindNext(bool goingDown, cbStyledTextCtrl* control, cbFindReplaceData* data, bool selected) {
    if (!control) {
        cbEditor *ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
        if (ed) { control = ed->GetControl(); }
    }

    if (!control) { return -1; }

    if (!data) {
        data = m_LastFindReplaceData;
        // FindNext/Previous called from Search menu (F3/Shift-F3)
        if (data) { data->findInFiles = false; }
    }

    if (!data) {
        if (selected) {
            m_LastFindReplaceData = new cbFindReplaceData;
            data = m_LastFindReplaceData;
            data->findInFiles = false;
        } else { return ShowFindDialog(false, false); }
    }

    if (!data->findInFiles) {
        if (selected) {
            data->start = 0;
            data->end = 0;
            data->findText = control->GetSelectedText();
            data->replaceText = wxEmptyString;
            data->eolMode = wxSCI_EOL_LF;
            data->multiLine = false;
            data->fixEOLs = false;
            data->startFile = false;
            data->findInFiles = false;
            data->matchWord = false;
            data->startWord = false;
            data->matchCase = false;
            data->regEx = false;
            data->originEntireScope = false;
            data->scope = 0; // global
            data->searchPath = wxEmptyString;
            data->searchMask = wxEmptyString;
            data->recursiveSearch = false;
            data->searchProject = false;
            data->searchTarget = false;
            data->hiddenSearch = false;
            data->initialreplacing = false;
            data->NewSearch = false;
        }

        wxString phraseAtCursor = control->GetSelectedText();

        if (!data->findUsesSelectedText && !selected) {
            // The mandrav find behavior
            // change findText to selected text (if any text is selected and no search text was set before)
            // 曼德拉夫发现行为?
            // 将findText更改为选定文本(如果选择了任何文本,并且之前未设置搜索文本)
            if (!phraseAtCursor.IsEmpty() && data->findText.IsEmpty()) { data->findText = phraseAtCursor; }
        } else {
            // The tiwag find behavior
            // change findText to selected text (if any text is selected)
            // tiwag发现行为
            // 将findText更改为选定文本(如果选择了任何文本)

            if (!phraseAtCursor.IsEmpty()) {
                data->findText = phraseAtCursor;
                //search from cursor
                // 从光标搜索
                data->originEntireScope = false;
                // global ("selected text" is useful only from Find Dialog)
                //全局("选定文本"仅在"查找"对话框中有用)
                data->scope = 0;
            }
        }
    }
    data->directionDown = goingDown;
    return Find(control, data);
}

int FindReplace::FindSelectedText(bool goingDown) {
    cbEditor *ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
    if (ed) {
        cbStyledTextCtrl *stc = ed->GetControl();
        wxString text = stc->GetSelectedText();
        if (!text.size()) {
            //selecting word at current cursor position
            // 在当前光标位置选择单词
            int iCurrentPos = stc->GetCurrentPos();

            stc->SetSelectionStart(stc->WordStartPosition(iCurrentPos, true));
            stc->SetSelectionEnd(stc->WordEndPosition(iCurrentPos, true));
        }

        return FindNext(goingDown, nullptr, nullptr, true);
    }

    return -1;
}
